using System;
using System.Collections;
using System.Globalization;

using Evaluant.OPath;
using Evaluant.OPath.Expressions;
using Evaluant.Uss;

namespace Evaluant.Uss.Common
{
    class NavigationVisitor : OPathVisitor
    {
        #region Members

        internal object _NodeValue;
        internal EntitySet _Roots;

        private Models.Model _Model;
        private Stack _ExprLevel;   // indicate if transforming a path which return an node set or an expression which return a scalar
        private const int EXPR = 0;
        private const int PATH = 1;
        private Type _expectedType;

        private NumberFormatInfo _NumberFormatInfo;

        #endregion

        #region ctor

        public NavigationVisitor()
            : this(new EntitySet(), null, false)
        {
        }

        public NavigationVisitor(EntitySet roots, Models.Model model, bool isExpression)
        {
            _NodeValue = null;
            _Roots = roots;
            _Model = model;
            _expectedType = null;
            _ExprLevel = new Stack();
            _ExprLevel.Push(isExpression ? EXPR : PATH);

            _NumberFormatInfo = new NumberFormatInfo();
            _NumberFormatInfo.NumberDecimalSeparator = ".";
        }

        #endregion

        #region Visit

        public override void Visit(Path path)
        {
            bool isNewPath = ((int)_ExprLevel.Peek() == EXPR);
            _ExprLevel.Push(PATH);

            if (isNewPath)
            {
                string type = path.Identifiers[0].Value;

                EntitySet roots = new EntitySet();
                string[] rootsTypes = _Model.GetTreeAsArray(type);

                foreach (Entity entity in _Roots)
                {
                    if (Array.IndexOf(rootsTypes, entity.Type) != -1)
                        roots.Add(entity);
                }

                path.Identifiers[0].Value = "this";

                Navigator navigator = new Navigator(roots);
                _NodeValue = navigator.SelectElements(path);
            }
            else
            {
                Navigator navigator = new Navigator(_Roots);
                _NodeValue = navigator.SelectElements(path);
            }

            if (_NodeValue is object[] && ((object[])_NodeValue).Length > 0 && !(((object[])_NodeValue)[0] is Entity))
                _expectedType = ((object[])_NodeValue)[0].GetType();

            _ExprLevel.Pop();
        }

        public override void Visit(Identifier identifier)
        {
        }

        public override void Visit(Function function)
        {
            function.Path.Accept(this);
            object pathValue = _NodeValue;

            switch (function.Type)
            {
                case FunctionEnum.Average:
                    double avg = 0;
                    foreach (object f in (object[])pathValue)
                        avg += Convert.ToDouble(f);

                    if (((object[])pathValue).Length > 0)
                        _NodeValue = avg / ((object[])pathValue).Length;
                    else
                        _NodeValue = avg;
                    break;

                case FunctionEnum.Count:
                    _NodeValue = Convert.ToDecimal(((object[])pathValue).Length);

                    //if (_expectedType == typeof(Entity))
                    //    _expectedType = typeof(Decimal);
                    break;

                case FunctionEnum.Exists:
                    _NodeValue = ((object[])pathValue).Length > 0;
                    break;

                case FunctionEnum.IsNull:
                    _NodeValue = ((object[])pathValue).Length == 0;
                    break;

                case FunctionEnum.Max:
                    _NodeValue = pathValue;

                    foreach (object f in (object[])pathValue)
                        _NodeValue = (_NodeValue == pathValue) || (Comparer.Default.Compare(_NodeValue, f) == -1) ? f : _NodeValue;

                    break;

                case FunctionEnum.Min:
                    _NodeValue = pathValue;

                    foreach (object f in (object[])pathValue)
                        _NodeValue = (_NodeValue == pathValue) || (Comparer.Default.Compare(_NodeValue, f)) == 1 ? f : _NodeValue;

                    break;

                case FunctionEnum.Sum:
                    decimal sum = 0;
                    foreach (object f in (object[])pathValue)
                        sum += Convert.ToDecimal(f);

                    if (_expectedType != null)
                        _NodeValue = Convert.ChangeType(sum, _expectedType);
                    else
                        _NodeValue = sum;
                    break;
            }
        }

        public override void Visit(BinaryOperator binaryop)
        {
            binaryop.LeftOperand.Accept(this);
            object left = _NodeValue;

            if (left.GetType().IsArray) // can be an array if it is a Path
            {
                object[] values = (object[])left;
                left = values.Length == 0 ? null : values[0];
            }

            binaryop.RightOperand.Accept(this);
            object right = _NodeValue;

            if (right.GetType().IsArray) // can be an array if it is a Path
            {
                object[] values = (object[])right;
                right = values.Length == 0 ? null : values[0];
            }

            switch (binaryop.Type)
            {
                case BinaryOperatorEnum.And:
                    if (left == null || right == null)
                        _NodeValue = false;
                    else
                        _NodeValue = (bool)left && (bool)right;
                    break;

                case BinaryOperatorEnum.Or:
                    if (left == null)
                    {
                        _NodeValue = (bool)right;
                        break;
                    }

                    if (right == null)
                    {
                        _NodeValue = (bool)left;
                        break;
                    }

                    _NodeValue = (bool)left || (bool)right;

                    break;

                case BinaryOperatorEnum.Div:
                    if (left == null || right == null)
                    {
                        _NodeValue = null;
                        break;
                    }

                    _NodeValue = Convert.ToDecimal(left) / Convert.ToDecimal(right);
                    if (_expectedType != null)
                        _NodeValue = Convert.ChangeType(_NodeValue, _expectedType);
                    break;

                case BinaryOperatorEnum.Equal:
                    if (left == null || right == null)
                    {
                        _NodeValue = (left == right);
                        break;
                    }

                    if (left.GetType().IsEnum)
                        _NodeValue = (left.ToString() == right.ToString());
                    else
                        _NodeValue = Comparer.Default.Compare(left, Convert.ChangeType(right, left.GetType())) == 0;
                    break;

                case BinaryOperatorEnum.Greater:
                    // Use the type of the left operand
                    _NodeValue = Comparer.Default.Compare(left, Convert.ChangeType(right, left.GetType())) > 0;
                    break;

                case BinaryOperatorEnum.GreaterOrEqual:
                    _NodeValue = Comparer.Default.Compare(left, Convert.ChangeType(right, left.GetType())) >= 0;
                    break;

                case BinaryOperatorEnum.Lesser:
                    _NodeValue = Comparer.Default.Compare(left, Convert.ChangeType(right, left.GetType())) < 0;
                    break;

                case BinaryOperatorEnum.LesserOrEqual:
                    _NodeValue = Comparer.Default.Compare(left, Convert.ChangeType(right, left.GetType())) <= 0;
                    break;

                case BinaryOperatorEnum.Minus:
                    _NodeValue = Convert.ToDecimal(left) - Convert.ToDecimal(right);
                    break;

                case BinaryOperatorEnum.Modulo:
                    _NodeValue = Convert.ToDecimal(left) % Convert.ToDecimal(right);
                    break;

                case BinaryOperatorEnum.NotEqual:
                    if (left == null || right == null)
                    {
                        _NodeValue = left != right;
                        break;
                    }

                    if (left.GetType().IsEnum)
                        _NodeValue = (left.ToString() == right.ToString());
                    else
                        _NodeValue = Comparer.Default.Compare(left, Convert.ChangeType(right, left.GetType())) != 0;

                    break;

                case BinaryOperatorEnum.Plus:
                    _NodeValue = Convert.ToDecimal(left) + Convert.ToDecimal(right);
                    break;

                case BinaryOperatorEnum.Times:
                    _NodeValue = Convert.ToDecimal(left) * Convert.ToDecimal(right);
                    break;

                case BinaryOperatorEnum.Contains:
                    _NodeValue = Convert.ToString(left).IndexOf(Convert.ToString(right)) != -1;
                    break;

                case BinaryOperatorEnum.BeginsWith:
                    _NodeValue = Convert.ToString(left).StartsWith(Convert.ToString(right));
                    break;

                case BinaryOperatorEnum.EndsWith:
                    _NodeValue = Convert.ToString(left).EndsWith(Convert.ToString(right));
                    break;
            }
        }

        public override void Visit(UnaryOperator unaryop)
        {
            unaryop.Operand.Accept(this);

            switch (unaryop.Type)
            {
                case UnaryOperatorEnum.Not:
                    _NodeValue = !(bool)_NodeValue;
                    break;

                case UnaryOperatorEnum.Minus:
                    _NodeValue = -Convert.ToDecimal(_NodeValue);
                    break;
            }
        }

        public override void Visit(Call call)
        {
            switch (call.Name)
            {
                case "id":
                    _NodeValue = false;
                    for (int i = 0; i < call.Operands.Count; i++)
                    {
                        NavigationVisitor checker = new NavigationVisitor();
                        call.Operands[i].Accept(checker);
                        if (_Roots[0].Id == (string)checker._NodeValue)
                        {
                            _NodeValue = true;
                            return;
                        }
                    }
                    break;


                case "eval":
                    call.Operands[0].Accept(this);
                    break;
            }
        }

        public override void Visit(Value val)
        {
            switch (val.Type)
            {
                case ValueEnum.Boolean:
                    _NodeValue = bool.Parse(val.Text);
                    break;

                case ValueEnum.Date:
                    _NodeValue = DateTime.Parse(val.Text);
                    break;

                case ValueEnum.Float:
                    _NodeValue = decimal.Parse(val.Text, _NumberFormatInfo);
                    break;

                case ValueEnum.Integer:
                    // Use float parser as the binary operations will be applied as floats
                    _NodeValue = decimal.Parse(val.Text);
                    break;

                case ValueEnum.String:
                    _NodeValue = val.Text;
                    break;
            }
        }

        #endregion
    }
}
